import { useDebounceFn, useMemoizedFn } from 'ahooks';
import { Layout } from 'antd';
import { cloneDeep } from 'lodash';
import React, { forwardRef, useState } from 'react';

import { OperationType } from '@/pages/TemplateEditor/constant';
import {
  LayerInfoFE,
  RealTimePreviewProps,
  ScreenRefType
} from '@/pages/TemplateEditor/interface';

import Layers from './components/Layers';
import Screen from './components/Screen';
import ScreenController from './components/ScreenController';
import {
  DEFAULT_ACTIVE_LAYER,
  defaultZoomSizeIndex,
  MAX_HISTORY_COUNT
} from './constant';
import { emitter, EVENT_TYPE } from './events';
import styles from './index.module.less';
import { ScreenControllerRealTimePreviewProps } from './interface';
import { isNotCroppedLayer } from './utils';

const { Content } = Layout;

interface TemplateEffectProps {
  screenSize: string;
  layers: LayerInfoFE[];
  productImageIndex: number;
  operationType: OperationType;
  setScreenSize: (size: string) => void;
  onLayersChange: (layers: LayerInfoFE[]) => void;
  setProductImageIndexIndex: (index: number) => void;
  realTimePreview: (props: RealTimePreviewProps) => Promise<LayerInfoFE[]>;
}

const TemplateEffect = forwardRef(
  (props: TemplateEffectProps, screenRef: ScreenRefType): JSX.Element => {
    const {
      screenSize,
      layers = [],
      productImageIndex,
      operationType,
      setScreenSize,
      onLayersChange,
      setProductImageIndexIndex
    } = props;

    const realTimePreview = useMemoizedFn(props.realTimePreview);

    const [zoomIndex, setZoomIndex] = useState(defaultZoomSizeIndex);
    const [activeLayerKey, setActiveLayerKey] = useState(
      operationType === OperationType.Create ? DEFAULT_ACTIVE_LAYER : null
    );
    const [cropLayerKey, setCropLayerKey] = useState(null);
    const [{ history, historyIndex }, setLayersHistory] = useState({
      history: [] as LayerInfoFE[][],
      historyIndex: 0
    });

    const handleHistoryIndexChange = (newIndex: number): void => {
      const newLayers = cloneDeep(history[newIndex]);
      if (!newLayers) {
        return;
      }

      // 更新右侧form数据
      emitter.emit(EVENT_TYPE.resetLayerForm, newLayers);

      setLayersHistory({ history, historyIndex: newIndex });

      onLayersChange(newLayers);
      if (newLayers.some((layer) => layer.data?.needPreview)) {
        getRealtimeData(newLayers);
      }

      screenRef.current.rerenderScreen(newLayers);
    };

    const { run: debounceChangeHistory } = useDebounceFn(
      (newLayers: LayerInfoFE[]): void => {
        setLayersHistory(({ history, historyIndex }) => {
          const newHistory = history.slice(0, historyIndex + 1);
          newHistory.push(newLayers);
          if (newHistory.length > MAX_HISTORY_COUNT) {
            newHistory.shift();
          }
          return { history: newHistory, historyIndex: newHistory.length - 1 };
        });
      },
      { wait: 300 }
    );

    const getRealtimeData = async (newLayers: LayerInfoFE[]): Promise<void> => {
      // 只需要发送当前编辑的 layer 数据
      const layersPayload = newLayers.filter(
        (layer) => layer.data.needPreview === true
      );
      const realTimePreviewData = await realTimePreview({
        layers: layersPayload,
        index: productImageIndex
      });
      onLayersChange(realTimePreviewData);
      screenRef.current.rerenderScreen(realTimePreviewData);
    };

    const handleUpdateLayers = async (
      newLayers: LayerInfoFE[],
      needRealTimePreview: boolean = false,
      activeKey?: string,
      needRerender: boolean = true,
      needRecordHistory: boolean = true
    ): Promise<void> => {
      // 在更新layers之前，先将history默认值加上，如果存在未crop的项，则还未处理完默认值
      if (!history.length && !layers.some(isNotCroppedLayer)) {
        setLayersHistory({ history: [layers], historyIndex: 0 });
      }

      onLayersChange(newLayers);

      if (needRecordHistory) {
        debounceChangeHistory(newLayers);
      }

      if (needRealTimePreview) {
        getRealtimeData(newLayers);
      }

      if (needRerender) {
        screenRef.current.rerenderScreen(newLayers, activeKey);
      }

      activeKey && setActiveLayerKey(activeKey);
    };

    const realTimePreviewAndSetNewLayers = async (
      newOptions: ScreenControllerRealTimePreviewProps
    ): Promise<void> => {
      setProductImageIndexIndex(newOptions.index);
      const realTimePreviewData = await realTimePreview({
        ...newOptions,
        layers
      });
      onLayersChange(realTimePreviewData);
      screenRef.current.rerenderScreen(realTimePreviewData);
    };

    const changeCropLayer = useMemoizedFn(
      (key?: string, newLayers?: LayerInfoFE[]): void => {
        setCropLayerKey(key);
        setTimeout(() => screenRef.current.rerenderScreen(newLayers || layers));
      }
    );

    return (
      <Layout className={styles.wrapper}>
        <Content className="h-full">
          <ScreenController
            zoomIndex={zoomIndex}
            productImageIndex={productImageIndex}
            setZoomIndex={setZoomIndex}
            realTimePreview={realTimePreviewAndSetNewLayers}
            historyCount={history.length}
            historyIndex={historyIndex}
            onHistoryIndexChange={handleHistoryIndexChange}
          />
          <Screen
            ref={screenRef}
            layers={layers}
            zoomIndex={zoomIndex}
            screenSize={screenSize}
            cropLayerKey={cropLayerKey}
            onUpdate={handleUpdateLayers}
            activeLayerKey={activeLayerKey}
            changeCropLayer={changeCropLayer}
            setActiveLayerKey={setActiveLayerKey}
          />
        </Content>
        <Layers
          screenRef={screenRef}
          layers={layers}
          screenSize={screenSize}
          activeKey={activeLayerKey}
          operationType={operationType}
          onUpdate={handleUpdateLayers}
          setScreenSize={setScreenSize}
          onSetActive={(key): void => {
            setActiveLayerKey(key);
            setTimeout(() => screenRef.current.rerenderScreen(layers));
          }}
          onSetCropLayerId={(key): void => changeCropLayer(key)}
        />
      </Layout>
    );
  }
);

export default TemplateEffect;
